
const fs = require('fs')
const MFS = require('memory-fs')
const path = require('path')
const webpack = require('webpack')
const express = require('express')
const { createBundleRenderer } = require('vue-server-renderer')

const clientConfig = require('./build/webpack.client.config')
const serverConfig = require('./build/webpack.server.config')

let template = fs.readFileSync('./src/index.template.html', 'utf-8')
let serverBundle
let clientManifest
let ready
let renderer
function updated({ serverBundle, clientManifest, template }) {
  // @see https://v2.ssr.vuejs.org/zh/guide/bundle-renderer.html#%E4%BC%A0%E5%85%A5-bundlerenderer
  renderer = createBundleRenderer(serverBundle, {
    runInNewContext: false, 
    template, 
    clientManifest
  })
}
let readyPromise = new Promise(r => ready = r)

// @see https://www.webpackjs.com/api/node/#compiler-instance
const clientCompiler = webpack(clientConfig)
// 这里找不回webpack3的文档了，webpack5中没找到这个用法，但不难看出是给Compiler对象注册钩子函数。
clientCompiler.plugin('done', () => {
  // 因为clientConfig是交给webpack-dev-middleware进行执行编译的，可从它的输出中读取内存生成的Client Bundle
  clientManifest = JSON.parse(devMiddleware.fileSystem.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-client-manifest.json'), 'utf-8'))
  if (serverBundle) {
    // 这里的因为webpack-dev-middleware会监听文件的变化再次编译，所以可能会被多次updated，用promise resolve不合适
    updated({ serverBundle, clientManifest, template })
    ready()
  }
})
// @see https://www.webpackjs.com/guides/development/#using-webpack-dev-middleware
const devMiddleware = require('webpack-dev-middleware')(clientCompiler, {
  publicPath: clientConfig.output.publicPath,
  noInfo: true,
})

// 基本同上
const serverCompiler = webpack(serverConfig)
// @see https://www.npmjs.com/package/memory-fs  A simple in-memory filesystem. 
const mfs = new MFS()
serverCompiler.outputFileSystem = mfs
serverCompiler.watch({}, () => {
  serverBundle = JSON.parse(mfs.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-server-bundle.json')))
  if (clientManifest) {
    updated({ serverBundle, clientManifest, template })
    ready()
  }
})


const app = express()

// @see https://www.webpackjs.com/guides/development/#using-webpack-dev-middleware
app.use(devMiddleware)

app.get('*', (req, res) => {
  // 等待编译完成读取到Server Bundle和Client Bundle
  readyPromise.then(() => {
    const context = { url: req.url }
    // @see https://v2.ssr.vuejs.org/zh/api/#renderer-rendertostring
    // 这里无需传入一个应用程序，因为在执行 bundle 时已经自动创建过。
    // 现在我们的服务器与应用程序已经解耦！
    renderer.renderToString(context, (err, html) => {
      // 处理异常……
      res.end(html)
    })
  })
})

app.listen(8084, () => {
  console.log('http://localhost:8084')
})